//expr.c
#include "ast.h"
#include "expr.h"

struct expr * expr_create ( 
  int kind, 
  struct expr *left, 
  struct expr *right, 
  const char * name, 
  int constant, 
  struct symbol * symbol, 
  int reg)
{
  struct expr *a = malloc(sizeof(*a));

	a->kind = kind;
	a->left = left;
	a->right = right;
	a->name = name;
	a->constant = constant;
        a->symbol = symbol;

	return a;
}

void expr_print(struct expr * e)
{
  if (!e) return;
  switch(e->kind)
  {
  //comparators
    case EXPR_KIND_LT:      
      expr_print(e->left);
      printf("<");
      expr_print(e->right);
      break;
    case EXPR_KIND_LE:
      expr_print(e->left);
      printf("<=");
      expr_print(e->right);
      break;
    case EXPR_KIND_EQ     :
      expr_print(e->left);
      printf("==");
      expr_print(e->right);
      break;
    case EXPR_KIND_NE     : 
      expr_print(e->left);
      printf("!=");
      expr_print(e->right);
      break;
    case EXPR_KIND_GT     : 
      expr_print(e->left);
      printf(">");
      expr_print(e->right);
      break;
    case EXPR_KIND_GE     : 
      expr_print(e->left);
      printf(">=");
      expr_print(e->right);
      break;

    //binary ops
    case EXPR_KIND_ADD    : 
      expr_print(e->left);
      printf("+");
      expr_print(e->right);
      break;
    case EXPR_KIND_SUB    :  
      expr_print(e->left);
      printf("-");
      expr_print(e->right);
      break;
    case EXPR_KIND_MUL    :  
      expr_print(e->left);
      printf("*");
      expr_print(e->right);
      break;
    case EXPR_KIND_DIV:
      expr_print(e->left);
      printf("/");
      expr_print(e->right);
      break;
    case EXPR_KIND_LOGICALAND:
      expr_print(e->left);
      printf(" && ");
      expr_print(e->right);
      break;
    case EXPR_KIND_LOGICALOR:
      expr_print(e->left);
      printf(" || ");
      expr_print(e->right);
      break;
    case EXPR_KIND_MOD: 
      expr_print(e->left);
      printf("%");
      expr_print(e->right);
      break;
    case EXPR_KIND_POW: 
      expr_print(e->left);
      printf("^");
      expr_print(e->right);
      break;
    
//UNARY OPS
    case EXPR_KIND_NEG: 
      printf(" -");
      expr_print(e->right );
      break;
    case EXPR_KIND_INCR:
      expr_print(e->right );
      printf("++");
      break;
    case EXPR_KIND_DECR:
      expr_print(e->right );
      printf("--");
      break;

//rando stuff
    case EXPR_KIND_ASSIGN :
      expr_print(e->left);
      printf(" = ");
      expr_print(e->right);
      break;
    case EXPR_KIND_CALL: 
      expr_print( e->left);
      printf("(");
      if (e->right) {
        expr_print(e->right); 
      }
      printf(")");
      break;
    case EXPR_KIND_ARG:
      expr_print(e->left);
      if (e->right)
      {
        printf(", ");
        expr_print(e->right);
      }
      //print left, print comma, print right, right is new arg tree
      break;
    case EXPR_KIND_NAME:
      printf(" %s ", e->name); 
      break;
    case EXPR_KIND_INT:       
	printf(" %d ", e->constant);
        break;
    case EXPR_KIND_CHAR: 
	if (e->constant == '\n') 
          printf(" '\\n' ");
	else if (e->constant == '\0') 
          printf(" '\\0' ");
        else 
          printf(" '%c' ", e->constant);
        break;
    case EXPR_KIND_BOOLEAN: 
	printf(" %s ", (e->constant) ? "true" : "false");
        break;
    case EXPR_KIND_STRING: 
	printf(" %s ", e->name);
        break;
    
  }

}


void expr_resolve( struct expr * e, int print)
{
  if (!e) return;
  expr_resolve(e->left,  print);
  expr_resolve(e->right, print);
  if (e->kind == EXPR_KIND_NAME)
  {
    struct symbol * s = scope_lookup(e->name);
    if (s)
    { 
      char *local  = "local";
      char *global = "global";
      char *param  = "param";
      char *var_kind;
      var_kind = (s->kind == SYMBOL_KIND_LOCAL) ? local : (s->kind == SYMBOL_KIND_GLOBAL) ? global : param ;  
      if (print)  printf(" %s resolves to %s variable %d\n", e->name, var_kind, s->which);
      symbol_print(s);
      e->symbol = s;
    } else {
      printf("Undefined reference: it looks like %s hasn't been properly declared prior to an attempt to use it\n", e->name);
      exit(1);
    }  
  }
}


struct type * expr_typecheck(struct expr * e)
{
  if (!e) return 0;
  struct type * INTIE  = type_create(TYPE_KIND_INTEGER, 0, 0);
  struct type * BOOLIE = type_create(TYPE_KIND_BOOLEAN, 0, 0);
  struct type * CHARLIE = type_create(TYPE_KIND_CHARACTER, 0, 0);
  struct type * VOIDIE = type_create(TYPE_KIND_VOID, 0, 0);
  struct type * FUNCKIE = type_create(TYPE_KIND_FUNCTION, 0, 0);
  struct type * L;
  struct type * R;
  struct type * result;
  struct symbol * s;
  switch (e->kind)
  {
    case EXPR_KIND_INT:
      result = type_create(TYPE_KIND_INTEGER, 0, 0);
      break;
    case EXPR_KIND_BOOLEAN:
      result = type_create(TYPE_KIND_BOOLEAN, 0, 0);
      break;
    case EXPR_KIND_CHAR:
      result =  type_create(TYPE_KIND_CHARACTER, 0, 0);
      break;
    case EXPR_KIND_STRING:
      result =  type_create(TYPE_KIND_STRING, 0, 0);
      break;
    case EXPR_KIND_NAME:
      if (s = scope_lookup(e->name) )
	if (s->type)
	  if(s->type->kind == TYPE_KIND_FUNCTION)
      {
        printf("error: function is being called with no parens.\n");
	exit(1);
      }
      result =  (e->symbol->type);
      break;
    case EXPR_KIND_ASSIGN:
      L = expr_typecheck(e->left);
      R = expr_typecheck(e->right);
      lval_check(e->left);
      if (type_compare(L, R)) result =  R;
      else {
        printf("Type mismatch: ");
        type_print(L);
        printf(" and ");
        type_print(R);
        printf(" are not the same type.\n ");
        exit(1);
      }
      break;
    case EXPR_KIND_ADD:
    case EXPR_KIND_SUB:
    case EXPR_KIND_MUL:
    case EXPR_KIND_DIV:
    case EXPR_KIND_MOD:
    case EXPR_KIND_POW:
      L = expr_typecheck(e->left);
      R = expr_typecheck(e->right);
      printf(" in arith: "); type_print(L); printf("  "); type_print(R); printf("\n");
      // both need to equal each other and equal integer:
      if (!  (type_compare(L, INTIE) && type_compare(R, INTIE) ) )
      {
        printf("Arithmetic operations only work on two integers.\n");
        printf("You tried on a ");
        type_print(L);
        printf(" and a ");
        type_print(R);
        printf(".\n");
        exit(1);
      }
      result =  L;
      break;
    case EXPR_KIND_LOGICALAND:
    case EXPR_KIND_LOGICALOR:
      L = expr_typecheck(e->left);
      R = expr_typecheck(e->right);
      if (!type_compare(L, BOOLIE) || !type_compare(R, BOOLIE) )
      {
        printf("Boolean operations only work on two boolean types.\n");
        printf("You tried on a ");
        type_print(L);
        printf(" and a ");
        type_print(R);
        printf(".\n");
        exit(1);
      }
      result =  BOOLIE;
      break;

    case EXPR_KIND_EQ:  //also booleans/chars
    case EXPR_KIND_NE:
      L = expr_typecheck(e->left);
      R = expr_typecheck(e->right);

      int isBool = type_compare(L, BOOLIE)  && type_compare(R, BOOLIE);
      int isInt  = type_compare(L, INTIE)   && type_compare(R, INTIE);
      int isChar = type_compare(L, CHARLIE) && type_compare(R, CHARLIE);
      if (!(isChar || isBool || isInt))
      {
        printf("error: Equality comparisions are restricted to pairs of ints, chars, or booleans.\n");
        printf("You tried on a ");
        type_print(L);
        printf(" and a ");
        type_print(R);
        printf(".\n");
        exit(1);
      }

      result =  BOOLIE;
      break;
    case EXPR_KIND_GT:
    case EXPR_KIND_GE:
    case EXPR_KIND_LT:
    case EXPR_KIND_LE:
      L = expr_typecheck(e->left);
      R = expr_typecheck(e->right);
      //check for integer
      if (  ! (type_compare(L, INTIE) && type_compare(R, INTIE)  ) ) 
      {
        printf("error: Inequality comparison operations only work on two integer types.\n");
        printf("You tried on a ");
        type_print(L);
        printf(" and a ");
        type_print(R);
        printf(".\n");
        exit(1);
      }
      result =  BOOLIE;
      break;
    case EXPR_KIND_CALL:
      
      s = e->left->symbol;
      if (s->type->kind != TYPE_KIND_FUNCTION)
      {
        printf("Error: %s not recognized as a function name\n.", e->left->name);
        exit(1);
      } 
      param_list_typecheck(s->type->params, e->right);
      result =  s->type->rtype;
//      printf("%d\n", result->kind);
      break;
    case EXPR_KIND_NEG:      
      R = expr_typecheck(e->right);      
      if (!type_compare(R, INTIE) )
      {
        printf("error: Negation is only valid on integer types.\n");
        printf("You tried on a ");
        type_print(R);
        printf(".\n");
        exit(1);        
      }
      result =  R;
      break;
    case EXPR_KIND_INCR:
    case EXPR_KIND_DECR:
      L = expr_typecheck(e->left);
      lval_check(L);
      //need only check one side of branch for integer-ness
      if (!type_compare(L, INTIE) )
      {
        printf("error: Incrementers and decrementers are only valid on integer types.\n");
        printf("You tried on a ");
        type_print(L);
        printf(".\n");
        exit(1);        
      }
      result =  L;
      break;      
    case EXPR_KIND_ARG:
      L =  expr_typecheck(e->left);
      if (type_compare(L, VOIDIE) )
      {
        printf("error: Args cannot have type void.\n");
        exit(1);
      }
      R =  expr_typecheck(e->right);
      result = L;
      break;

  }
  return result;
}


int isFunc(struct expr * e)
{
  struct symbol * sym;
  if (!e) return 0;
  if (e->kind == EXPR_KIND_NAME)
  {
    sym = scope_lookup(e->name);
    if (sym)
      if(sym->type)
	if(sym->type->kind == TYPE_KIND_FUNCTION)
          return 1;
    /*else { 
      printf("is this:"); 
      printf("|%d|", e->symbol->type->kind);
      printf("%s", e->name);
    }*/
  }
  return 0;
}

int lval_check(struct expr * e)
{
  if (!e) return 0;

  if (e->kind == EXPR_KIND_NAME) 
  {
    //check that function is not in lval
    if ( isFunc(e) )
    {
      printf("Error: assignment to non-lvalue expression.\n");
      exit(1);
    } else {
      return 1;     
    }
  }
  else
  {
    printf("Error: assignment to non-lvalue expression.\n");
    exit(1);
  }

}


void expr_generate_strings(struct expr  e)
{
  if (!e) return;
  expr_generate_strings(e->left);  
  expr_generate_strings(e->right);
  if (e->kind == EXPR_KIND_STRING)
    {
      int n = label_create();
      printf(".s%d:\n.string\"%s\"\n", n, e->name);
      sprintf(temp, ".s%d",n);
      e->name=strdup(temp);
    }

}

void expr_codegen( FILE *file, struct expr *e )
{
  if (!e) return;

  switch(e->kind)
    {
    case EXPR_KIND_INT:
    case EXPR_KIND_BOOL:
    case EXPR_KIND_CHAR:
      e->reg = register_alloc();
      fprintf(file, "movl $%d, %%%s\n", e->constant, register_name(e->reg));
      break;
    case EXPR_KIND_STRING:
      e->reg = register_alloc();
      fprintf(file, "movl $.s%d, %%%s\n", e->constant, register_name(e->reg));
      break;
    case EXPR_KIND_NAME:
      e->reg = register_alloc();
      fprintf(file, "movl %s, %s\n", symbol_code(e->symbol), register_name(e->reg));
      break;
    case EXPR_KIND_ADD:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      e->reg = e->right->reg;
      fprintf(file, "add %s, %s\n", register_name(e->right->reg),  register_name(e->left->reg));
      register_free(e->left->reg);
      break;
    case EXPR_KIND_SUB:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      e->reg = e->left->reg;
      fprintf(file, "sub %s, %s\n", register_name(e->left->reg),  register_name(e->right->reg));
      register_free(e->right->reg);
      break;
    case EXPR_KIND_DIV:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      fprintf(file, "PUSH %%edx\n");
      fprintf(file, "CLD\n");
      fprintf(file, "mov %s, %%eax\n", register_name(e->left->reg));
      fprintf(file, "idiv %s\n", register_name(e->right->reg));
      e->reg = register_alloc();
      fprintf(file, "mov %%eax, %s\n", register_name(e->reg));
      fprintf(file, "POP %%edx\n");
      register_free(e->left->reg);
      register_free(e->right->reg);
      break;
    case EXPR_KIND_MOD:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      fprintf(file, "PUSH %%edx\n");
      fprintf(file, "CLD\n");
      fprintf(file, "mov %s, %%eax\n", register_name(e->left->reg));
      fprintf(file, "idiv %s\n", register_name(e->right->reg));
      e->reg = register_alloc();
      fprintf(file, "mov %%edx, %s\n", register_name(e->reg));
      fprintf(file, "POP %%edx\n");
      register_free(e->left->reg);
      register_free(e->right->reg);
      break;
    case EXPR_KIND_MUL:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      fprintf(file, "mov %s, %%eax\n", register_name(e->left->reg));
      fprintf(file, "imul %s\n", register_name(e->right->reg));
      e->reg = register_alloc();
      fprintf(file, "mov %%eax, %s\n", register_name(e->reg));
      register_free(e->left->reg);
      register_free(e->right->reg);
      break;
    case EXPR_KIND_INCR:
      expr_codegen(file, e->left);
      fprintf(file, "mov %s, %%eax\n", register_name(e->left->reg));
      fprintf(file, "inc %%eax\n");
      fprintf(file, "mov %%eax, %s\n", register_name(e->left->reg));
      break;
    case EXPR_KIND_DECR:
      expr_codegen(file, e->left);
      fprintf(file, "mov %s, %%eax\n", register_name(e->left->reg));
      fprintf(file, "dec %%eax\n");
      fprintf(file, "mov %%eax, %s\n", register_name(e->left->reg));
      break;	
    case EXPR_KIND_LOGICALAND:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      e->reg = e->right->reg;
      fprintf(file, "and %s, %s\n", register_name(e->right->reg),  register_name(e->left->reg));
      register_free(e->left->reg);
      break;
    case EXPR_KIND_LOGICALOR:
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      e->reg = e->right->reg;
      fprintf(file, "or %s, %s\n", register_name(e->right->reg),  register_name(e->left->reg));
      register_free(e->left->reg);
      break;
    case EXPR_KIND_NEG:
      expr_codegen(file, e->right);
      e->reg = e->right->reg;
      fprintf(file, "neg %s\n", register_name(e->right->reg));
      break;
    case EXPR_KIND_LT:
      expr_gen_comparison(file, e, "JL");
      break;
    case EXPR_KIND_GT:
      expr_gen_comparison(file, e, "JG");
      break;
    case EXPR_KIND_LE:
      expr_gen_comparison(file, e, "JLE");
      break;
    case EXPR_KIND_GE:
      expr_gen_comparison(file, e, "JGE");
      break;
    case EXPR_KIND_EQ:
      expr_gen_comparison(file, e, "JE");
      break;
    case EXPR_KIND_NE:
      expr_gen_comparison(file, e, "JNE");
      break;
    case EXPR_KIND_ARG:
      expr_codegen(e->right);
      expr_codegen(e->left);
      fprinf(file, "PUSHL %s\n", register_name(e->left->reg));
      reg_free(e->left->reg);
      break;
    case EXPR_KIND_CALL:
      expr_codegen(e->right);
      fprintf(file, "CALL %s\n", e->left-name);
      fprintf(file, "add $(4*%d), %%esp", param_list_size(e->left->symbol->type->params));
      e->reg = register_alloc();
      fprintf("movl %%eax, %s", register_name(e->reg));
    case EXPR_KIND_ASSIGN:
      expr_codegen(e->right);
      e->reg = e->right->reg;
      fprintf("movl $s, $s", register_name(e->right->reg), symbol_code(e->left->symbol));
      break;
}

int param_list_size(struct param_list * p)
{
  if (!p) return 0;
  return param_list_size(p->next)+1;
}

void expr_gen_comparison(FILE * file, struct expr * e, const char * cmp)
{
      expr_codegen(file, e->left);
      expr_codegen(file, e->right);
      int x = label_create();
      int y = label_create();
      e->reg = register_alloc();
      
      fprintf(file, "CMPL %s, %s\n", register_name(e->right->reg),  register_name(e->left->reg));
      fprintf(file, "%s .L%d\n", cmp, x);
      fprintf(file, "movl $0, %s\n", register_name(e->reg);
      fprintf(file, "JMP .L%d\n", y);
      fprintf(file, ".L%d: movl $1, %s\n", x, register_name(e->reg));
      fprintf(file, ".L%d:\n", y);
      register_free(e->left->reg);
      register_free(e->right->reg);
}

void expr_codegen_constant(FILE * file, struct expr * e)
{
  if(!e) return;
  switch (e->kind)
  {
    case EXPR_KIND_INT:
    case EXPR_KIND_CHAR:
    case EXPR_KIND_BOOLEAN:
      fprintf(file, ".data\n");
      fprintf(file, ".globl %s\n", e->name);
      fprintf(file, "%s:\n", e->name);
      fprintf(file, ".long %d\n", e->constant);
      break;
    case EXPR_KIND_STRING:
      fprintf(file, ".data\n");
      fprintf(file, ".globl %s\n", e->name);
      fprintf(file, "%s:\n", e->name);
      fprintf(file, ".long %d\n", e->constant);
  }
}














